package com.class5.springboot.frame.repo.tool.xml;

import java.io.File;

import java.io.FileInputStream;
import java.io.InputStream;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.class5.springboot.frame.repo.exception.RepositoryException;
import com.class5.springboot.util.StringUtils;
import org.apache.commons.io.IOUtils;
import org.jdom.Attribute;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;

/**
 * XML处理器
 *
 * @author Administrator
 */
public class DbProjectFactory {
    private DbProjectFactory() {
    }

    private static DbProjectFactory builder = null;

    public synchronized static DbProjectFactory get() {
        if (builder != null)
            return builder;
        builder = new DbProjectFactory();
        return builder;
    }

    //本地缓存
    private static Map<String, DbProject> localCache = new HashMap<String, DbProject>();

    /**
     * 编译xml配置
     *
     * @param xmlstr
     * @return
     */
    private DbProject build(String xmlstr) {
        String str = xmlstr;
        //去掉命名空间
        if (str.indexOf("<root ") >= 0) {
            int a = str.indexOf("<root ");
            String stra = str.substring(0, a);
            String strb = str.substring(str.indexOf(">", a) + 1);
            str = stra + "<root>" + strb;
        }
        StringReader sr = new StringReader(str);
        try {
            SAXBuilder sb = new SAXBuilder();
            Document doc = sb.build(sr);
            Element root = doc.getRootElement();
            if (root.getName().equals("root")) {
                root = root.getChild("database");
            }
            DbProject p = initDatabase(root);
            localCache.put(p.getProject(), p);
            return p;
        } catch (Exception e) {
            throw new RepositoryException("解析XML失败", e);
        }
    }

    /**
     * 编译xml配置
     *
     * @param file
     * @return
     */
    public DbProject build(File file) {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(file);
            return build(fis);
        } catch (Exception e) {
            throw new RepositoryException(e);
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (Exception e2) {
                }
            }
        }
    }

    /**
     * 编译xml配置
     *
     * @param xml
     * @return
     */
    public DbProject build(InputStream xml) {
        try {
            return build(IOUtils.toString(xml));
        } catch (Exception e) {
            throw new RepositoryException("解析数据定义文件失败", e);
        }
    }

    @SuppressWarnings("unchecked")
    private DbProject initDatabase(Element root) {
        DbProject prj = new DbProject();
        String project = checkNotEmpty(getAttr(root, "project"), "项目名不能为空");
        prj.setProject(project);
        prj.setPrefix(getAttr(root, "prefix"));
        //prj.setDbs(getAttrArray(root, "dbs", "默认数据库节点不能为空"));
        prj.setDbs(new String[]{getAttr(root, "masterdb")});
        prj.setEntityPackage(getAttrAndCheck(root, "entityPackage", "实体包entityPackage不能为空"));
        prj.setMasterdb(getAttrAndCheck(root, "masterdb", "主数据库不能为空"));
        prj.setNotes(getAttr(root, "notes"));
        List<Element> ls = root.getChildren();
        List<DbTable> tables = new ArrayList<DbTable>();
        Map<String, DbCodeGroup> codegmap = new HashMap<String, DbCodeGroup>();
        List<DbInit> datas = new ArrayList<>();
        for (Element em : ls) {
            if (em.getName().equals("table")) {
                tables.add(initTable(em, prj, root));
            } else if (em.getName().equals("codegroup")) {
                codegmap.put(getAttr(em, "class"), initCodeGroup(em));
            } else if (em.getName().equals("init")) {
                datas.add(initDbInitData(em, prj));
            }
        }
        prj.setInitdatas(datas);
        prj.setCodeGroup(codegmap);
        prj.setTables(tables);
        return prj;
    }

    @SuppressWarnings("unchecked")
    private DbInit initDbInitData(Element em, DbProject prj) {
        DbInit data = new DbInit();
        data.setEnv(getAttrWithDefault(em, "env", "prod"));
        data.setKey(getAttrAndCheck(em, "key", "初始化块的key必需设置"));
        data.setVersion(Integer.parseInt(getAttrAndCheck(em, "version", "初始化数据版本号必需给定")));
        List<Element> ls2 = em.getChildren("table");
        for (Element em2 : ls2) {
            DbInitTable table = new DbInitTable();
            table.setEnv(getAttrWithDefault(em, "env", data.getEnv()));
            table.setName(getAttrAndCheck(em2, "name", "init.table的name不能为空"));
            table.setPks(getAttr(em2, "pks").split(","));
            //处理record
            List<Map<String, String>> records = new ArrayList<Map<String, String>>();
            for (Object recObj : em2.getChildren("record")) {
                Element rec = (Element) recObj;
                Map<String, String> obj = new HashMap<String, String>();
                for (Object attrObject : rec.getAttributes()) {
                    Attribute attr = (Attribute) attrObject;
                    obj.put(attr.getName(), attr.getValue());
                }
                for (Object cemObject : rec.getChildren()) {
                    Element cem = (Element) cemObject;
                    obj.put(cem.getName(), cem.getTextTrim());
                }
                records.add(obj);
            }
            table.setRecords(records);
            data.getTables().add(table);
        }
        return data;
    }

    private DbCodeGroup initCodeGroup(Element em) {
        DbCodeGroup g = new DbCodeGroup();
        g.setClassName(getAttrAndCheck(em, "class", "字典组的class属性必需设置"));
        g.setType(getAttrWithDefault(em, "type", "string"));
        List<DbCodeItem> items = new ArrayList<DbCodeItem>();
        for (Object cemobj : em.getChildren("code")) {
            Element cem = (Element) cemobj;
            DbCodeItem item = new DbCodeItem();
            item.setField(getAttrAndCheck(cem, "field", "code的field属性不能为空").toUpperCase());
            item.setText(getAttrAndCheck(cem, "text", "code的text属性不能为空"));
            item.setValue(getAttrAndCheck(cem, "value", "code的value属性不能为空"));
            item.setNotes(getAttr(cem, "notes"));
            items.add(item);
        }
        g.setItems(items);
        return g;
    }

    private Element findColumnGroup(String name, Element root) {
        for (Object emobj : root.getChildren()) {
            Element em = (Element) emobj;
            if (em.getName().equals("columngroup") && name.equals(em.getAttributeValue("name"))) {
                return em;
            }
        }
        return null;
    }

    //初始化表的声明
    @SuppressWarnings("unchecked")
    private DbTable initTable(Element em, DbProject prj, Element root) {
        DbTable table = new DbTable();
        table.setName(getAttrAndCheck(em, "name", "名称不能为空").toLowerCase());
        if(prj.getPrefix() != null && !prj.getPrefix().isEmpty() && !table.getName().startsWith(prj.getPrefix())) {
            table.setName(prj.getPrefix() + table.getName());
        }
        table.setNoentity(Boolean.parseBoolean(getAttrWithDefault(em, "noentity", "false")));
        table.setComment(getAttrAndCheck(em, "comment", "表的描述不能为空"));
        table.setNotes(getAttr(em, "notes"));
        table.setCanales(Boolean.parseBoolean(getAttrWithDefault(em, "canales", "false")));
        table.setCanalkafka(Boolean.parseBoolean(getAttrWithDefault(em, "canalkafka", "false")));
        String flag = getAttr(em, "generateService");
        if (flag != null)
            table.setGenerateService(Boolean.parseBoolean(flag));
        if (table.getComment() != null) {
            table.setComment(table.getComment().replace("'", ""));
        }
        //处理实体类名
        table.setEntity(getAttr(em, "entity"));
        if (table.getEntity() == null) {
            String entity = table.getName();
            //去掉后面的数字
            if (entity.lastIndexOf("_") > 0) {
                String str = entity.substring(entity.lastIndexOf("_") + 1);
                if (str.matches("\\d+")) {
                    entity = entity.substring(0, entity.lastIndexOf("_"));
                }
            }
            entity = entity.substring(0, 1).toUpperCase() + entity.substring(1);
            table.setEntity(StringUtils.underlineToCamel(entity));
        }
        if (table.getEntity().indexOf(".") < 0) {
            table.setEntity(prj.getEntityPackage() + "." + table.getEntity());
        }

        //暂时取消分库分表支持
        //table.setPerNodeNum(Integer.parseInt(getAttrWithDefault(em, "perNodeNum", "1")));
        //table.setDbs(getAttrArray(em, "dbs", null));
        table.setPerNodeNum(1);
        table.setDbs(new String[]{prj.getMasterdb()});
        table.setMasterdb(prj.getMasterdb());
        if(getAttr(em, "oldnames") != null) {
            throw new RepositoryException("考虑到数据安全性，重命名已被取消支持，如确定需要变更名称，请与dba联系.");
        }
        if (table.getPerNodeNum() <= 0) {
            throw new RepositoryException("PerNodeNum 必需大于0");
        }

        //添加列
        List<Element> ls = em.getChildren();
        List<DbTableColumn> columns = new ArrayList<DbTableColumn>();
        List<DbTableIndex> indexes = new ArrayList<DbTableIndex>();
        for (Element cem : ls) {
            if (cem.getName().equals("column")) {
                columns.add(initTableColumn(cem, table.getName(), indexes));
            } else if (cem.getName().equals("group")) {
                String ref = cem.getAttributeValue("ref");
                Element group = findColumnGroup(ref, root);
                if (group == null) {
                    throw new RepositoryException("未找到名称为" + ref + "的columngroup在xml文件中");
                }
                for (Object cem2obj : group.getChildren("column")) {
                    Element cem2 = (Element) cem2obj;
                    columns.add(initTableColumn(cem2, table.getName(), indexes));
                }
            }
        }
        //添加系统实体默认列
        addDefaultColumn(columns,table.getName(),indexes);
        //添加gmt_create,gmt_modify
        /*Element gmt_create = new Element("column").setAttribute("name", "gmt_create")
                .setAttribute("type", "datetime")
                .setAttribute("insertDefault", "now()")
                .setAttribute("comment", "制单时间");
        columns.add(initTableColumn(gmt_create, table.getName(), indexes));
        Element gmt_modify = new Element("column").setAttribute("name", "gmt_modify")
                .setAttribute("type", "datetime")
                .setAttribute("updateDefault", "now()")
                .setAttribute("comment", "修改时间");
        columns.add(initTableColumn(gmt_modify, table.getName(), indexes));*/
		/*//添加数据同步标识字段
		Element gmt_sync = new Element("column").setAttribute("name", "gmt_sync")
				.setAttribute("type", "int")
				.setAttribute("comment", "数据同步标识列");
		columns.add(initTableColumn(gmt_sync, table.getName(), indexes));*/

        table.setColumns(columns);
        for (Element cem : ls) {
            if (cem.getName().equals("index")) {
                //组合索引
                DbTableIndex idx = initTableIndex(cem, columns);
                if (!indexes.contains(idx))
                    indexes.add(idx);
            }
        }
        table.setIndexes(indexes);
        return table;
    }

    //添加表默认列
    private void addDefaultColumn(List<DbTableColumn> columns, String tableName, List<DbTableIndex> indexes) {
        //添加建立时间
        Element createTime = new Element("column");
        createTime.setAttribute("name", "create_time");
        createTime.setAttribute("type", "timestamp");
        createTime.setAttribute("nullable", "false");
        createTime.setAttribute("default", "current_timestamp");
        createTime.setAttribute("comment", "创建时间");
        columns.add(initTableColumn(createTime, tableName, indexes));
        //添加更新时间列
        Element modifyTime = new Element("column");
        modifyTime.setAttribute("name", "modify_time");
        modifyTime.setAttribute("type", "timestamp");
        createTime.setAttribute("default", "current_timestamp");
        modifyTime.setAttribute("extra", "on update current_timestamp");
        modifyTime.setAttribute("comment", "更新时间");
        columns.add(initTableColumn(modifyTime, tableName, indexes));

    }

    private DbTableColumn initTableColumn(Element em, String tableName, List<DbTableIndex> indexes) {
        DbTableColumn col = new DbTableColumn();
        col.setName(getAttrAndCheck(em, "name", "名称不能为空"));
        col.setComment(getAttrAndCheck(em, "comment", "备注不能空"));
        col.setNotes(getAttr(em, "notes"));
        if (col.getComment() != null) {
            col.setComment(col.getComment().replace("'", ""));
        }
        col.setNullable(Boolean.valueOf(getAttrWithDefault(em, "nullable", "true")));
        col.setVersion(Boolean.valueOf(getAttrWithDefault(em, "version", "false")));
        col.setIdentity(Boolean.valueOf(getAttrWithDefault(em, "identity", "false")));
        if (col.isIdentity()) {
            col.setNullable(false);
        }

        if(getAttr(em, "oldnames") != null) {
            throw new RepositoryException("考虑到数据安全性，重命名已被取消支持，如确定需要变更名称，请与dba联系.");
        }
        String cname = col.getName();
        if (cname.startsWith("is_")) {
            cname = cname.substring(cname.indexOf("_") + 1);
        }
        col.setField(getAttrWithDefault(em, "field", StringUtils.underlineToCamel(cname)));
        col.setNofield(Boolean.parseBoolean(getAttrWithDefault(em, "nofield", "false")));
        col.setDefaultValue(getAttr(em, "default"));
        col.setRefCodeGroup(getAttr(em, "refCodeGroup"));
        col.setFileField("true".equalsIgnoreCase(getAttr(em, "fileField")));
        //如果不是主键，且有索引类型
        if (!col.isIdentity()) {
            String index = getAttr(em, "index");
            if (index != null) {
                DbTableIndex iobj = new DbTableIndex();
                iobj.setName("idx_" + col.getName());
                iobj.setColumns(new String[]{col.getName()});
                iobj.setType(index);
                indexes.add(iobj);
            }
        }
        String tstr = getAttrAndCheck(em, "type", "数据类型必需指定").toLowerCase();
        String len = getAttr(em, "len");
        //对历史XML兼容
        if(tstr.equals("file") || tstr.equals("files")) {
            tstr = "varchar";
            len = tstr.equals("file")?"40":"255";
        }else if(tstr.equals("bit") && len  == null) {
            len = "1";
        }else if(tstr.equals("bigint") && len == null) {
            len = "20";
        }else if(tstr.equals("int") && len == null) {
            len = "11";
        }else if(tstr.equals("varchar") && len == null) {
            len = "255";
        }
        if(len != null && !len.isEmpty() && tstr.indexOf("(") < 0) {
            tstr = tstr + "(" + len + ")";
        }
        col.setColumnType(tstr.trim().toLowerCase());
        col.setAutoIncByRepo("true".equalsIgnoreCase(getAttr(em, "autoIncByRepo")));
        col.setAutoIncrement("true".equalsIgnoreCase(getAttr(em, "autoIncrement")));
        if(!col.isAutoIncByRepo() && !col.isAutoIncrement()) {
            col.setAutoIncByRepo(true);
        }
        //添加extra
        String extra = getAttr(em, "extra");
        if(col.isAutoIncrement()) {
            //自增
            extra = (extra==null?"":extra) + " auto_increment";
        }
        if(extra != null)
            col.setExtra(extra.trim());
        //调用一下类型转换，以检查一下配置的类型是否合法
        col.getFieldType();
        //如果为能为空，且为数字，设置默认值为0
        if(!col.isNullable() && col.getDefaultValue() == null && col.isNumberField()) {
            col.setDefaultValue("0");
        }
        return col;
    }

    private DbTableIndex initTableIndex(Element em, List<DbTableColumn> cols) {
        DbTableIndex index = new DbTableIndex();
        //按列名进行排序
        index.setColumns(getAttrArray(em, "columns", "组合键的列不能为空"));
        if (index.getColumns() == null || index.getColumns().length == 0) {
            throw new RepositoryException("index元素必需指定columns");
        }
        index.setNotes(getAttr(em, "notes"));
        String name = getAttr(em, "name");
        if (name == null) {
            name = "idx_" + StringUtils.join(index.getColumns(), "_");
        }
        index.setName(name);
        //检查列是否存在
        for (String c : index.getColumns()) {
            boolean find = false;
            for (DbTableColumn col : cols) {
                if (col.getName().equals(c)) {
                    find = true;
                    break;
                }
            }
            if (!find) {
                throw new RepositoryException("没有定义列" + c + "，但在索引定义中有使用");
            }
        }
        return index;
    }

    private String checkNotEmpty(String str, String msg) {
        return StringUtils.checkNotEmpty(str, msg);
    }

    private String getAttr(Element em, String name) {
        String str = em.getAttributeValue(name);
        if (str != null && str.trim().isEmpty())
            return null;
        return str == null ? null : str.trim();
    }

    private String getAttrWithDefault(Element em, String name, String defvalue) {
        String s = getAttr(em, name);
        if (s == null || s.trim().equals(""))
            return defvalue;
        return s;
    }

    private String getAttrAndCheck(Element em, String name, String checkMsg) {
        String str = getAttr(em, name);
        if (str == null || str.trim().isEmpty()) {
            if (checkMsg != null) {
                throw new RepositoryException("获取元素" + em.getName() + "属性" + name + "出错，" + checkMsg);
            }
        }
        return str;
    }

    private String[] getAttrArray(Element root, String name, String checkmsg) {
        String s = root.getAttributeValue(name);
        if (s == null || s.trim().equals("")) {
            if (checkmsg != null) {
                throw new RepositoryException(root.getName() + "的" + name + "属性不能为空");
            }
            return null;
        }
        String[] strs = s.split(",");
        List<String> s2 = new ArrayList<String>();
        for (String s3 : strs) {
            if (!s3.trim().equals("")) {
                s2.add(s3.trim());
            }
        }
        if (s2.isEmpty())
            return null;
        return s2.toArray(new String[s2.size()]);
    }

    public static DbTable getByEntityClass(Class<?> cla) {
        for (DbProject p : localCache.values()) {
            DbTable table = p.getTableByEntity(cla.getName());
            if (table != null) {
                return table;
            }
        }
        return null;
    }

    public static DbTable getByTable(String name) {
        for (DbProject p : localCache.values()) {
            DbTable table = p.getTable(name);
            if (table != null) {
                return table;
            }
        }
        return null;
    }

    public DbCodeGroup getCodeGroup(String g) {
        for (DbProject p : localCache.values()) {
            DbCodeGroup group = p.getCodeGroup().get(g);
            if (group != null)
                return group;
        }
        return null;
    }

    public static Collection<DbProject> getAllProject() {
        return localCache.values();
    }

    public static DbProject get(String project) {
        return localCache.get(project);
    }
}